#include <gio/gio.h>

static GDBusNodeInfo *introspection_data = NULL;

/* Introspection data for the service we are exporting */
static const gchar introspection_xml[] =
        "<node>"
        "  <interface name='org.gtk.GDBus.TestPeerInterface'>"
        "    <method name='HelloWorld'>"
        "      <arg type='s' name='greeting' direction='in'/>"
        "      <arg type='s' name='response' direction='out'/>"
        "    </method>"
        "  </interface>"
        "</node>";

/* ---------------------------------------------------------------------------------------------------- */

static void
handle_method_call (GDBusConnection       *connection,
                    const gchar           *sender,
                    const gchar           *object_path,
                    const gchar           *interface_name,
                    const gchar           *method_name,
                    GVariant              *parameters,
                    GDBusMethodInvocation *invocation,
                    gpointer               user_data)
{
    if (g_strcmp0 (method_name, "HelloWorld") == 0)
    {
        const gchar *greeting;
        gchar *response;

        g_variant_get (parameters, "(&s)", &greeting);
        response = g_strdup_printf ("You said '%s'. KTHXBYE!", greeting);
        g_dbus_method_invocation_return_value (invocation,
                                               g_variant_new ("(s)", response));
        g_free (response);
        g_print ("Client said: %s\n", greeting);
    }
}

static const GDBusInterfaceVTable interface_vtable =
        {
                handle_method_call,
                NULL,
                NULL,
        };

/* ---------------------------------------------------------------------------------------------------- */

static void
connection_closed (GDBusConnection *connection,
                   gboolean remote_peer_vanished,
                   GError *Error,
                   gpointer user_data)
{
    g_print ("Client disconnected.\n");
    g_object_unref (connection);
}

static gboolean
on_new_connection (GDBusServer *server,
                   GDBusConnection *connection,
                   gpointer user_data)
{
    guint registration_id;
    GCredentials *credentials;
    gchar *s;

    credentials = g_dbus_connection_get_peer_credentials (connection);
    if (credentials == NULL)
        s = g_strdup ("(no credentials received)");
    else
        s = g_credentials_to_string (credentials);


    g_print ("Client connected.\n"
             "Peer credentials: %s\n"
             "Negotiated capabilities: unix-fd-passing=%d\n",
             s,
             g_dbus_connection_get_capabilities (connection) & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING);

    g_object_ref (connection);
    g_signal_connect (connection, "closed", G_CALLBACK (connection_closed), NULL);
    registration_id = g_dbus_connection_register_object (connection,
                                                         "/org/gtk/GDBus/TestObject",
                                                         introspection_data->interfaces[0],
                                                         &interface_vtable,
                                                         NULL,  /* user_data */
                                                         NULL,  /* user_data_free_func */
                                                         NULL); /* GError** */
    g_assert (registration_id > 0);

    return TRUE;
}

/* ---------------------------------------------------------------------------------------------------- */

static gboolean
allow_mechanism_cb (GDBusAuthObserver *observer,
                    const gchar *mechanism,
                    G_GNUC_UNUSED gpointer user_data)
{
    /*
     * In a production GDBusServer that only needs to work on modern Unix
     * platforms, consider requiring EXTERNAL (credentials-passing),
     * which is the recommended authentication mechanism for AF_UNIX
     * sockets:
     *
     * if (g_strcmp0 (mechanism, "EXTERNAL") == 0)
     *   return TRUE;
     *
     * return FALSE;
     *
     * For this example we accept everything.
     */

    g_print ("Considering whether to accept %s authentication...\n", mechanism);
    if (g_strcmp0 (mechanism, "EXTERNAL") == 0) {
        return TRUE;
    }

    return FALSE;
}

static gboolean
authorize_authenticated_peer_cb (GDBusAuthObserver *observer,
                                 G_GNUC_UNUSED GIOStream *stream,
                                 GCredentials *credentials,
                                 G_GNUC_UNUSED gpointer user_data)
{
    gboolean authorized = FALSE;

    g_print ("Considering whether to authorize authenticated peer...\n");

    if (credentials != NULL)
    {
        GCredentials *own_credentials;
        gchar *credentials_string = NULL;

        credentials_string = g_credentials_to_string (credentials);
        g_print ("Peer's credentials: %s\n", credentials_string);
        g_free (credentials_string);

        own_credentials = g_credentials_new ();

        credentials_string = g_credentials_to_string (own_credentials);
        g_print ("Server's credentials: %s\n", credentials_string);
        g_free (credentials_string);

        if (g_credentials_is_same_user (credentials, own_credentials, NULL))
            authorized = TRUE;

        g_object_unref (own_credentials);
    }

    if (!authorized)
    {
/* In most servers you'd want to reject this, but for this example
 * we allow it. */
        g_print ("A server would often not want to authorize this identity\n");
        g_print ("Authorizing it anyway for demonstration purposes\n");
        authorized = TRUE;
    }

    return authorized;
}

/* ---------------------------------------------------------------------------------------------------- */

int main (int argc, char *argv[]) {
    gint ret = 1;
    gchar opt_address[] = "unix:abstract=/org/gtk/GDBus/address";
    GError *error;


    /* We are lazy here - we don't want to manually provide
     * the introspection data structures - so we just build
     * them from XML.
     */
    introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
    g_assert (introspection_data != NULL);

    GDBusAuthObserver *observer;
    GDBusServer *server;
    gchar *guid;
    GMainLoop *loop;
    GDBusServerFlags server_flags;

    guid = g_dbus_generate_guid();

    server_flags = G_DBUS_SERVER_FLAGS_NONE;
//        if (opt_allow_anonymous)
//            server_flags |= G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS;

    observer = g_dbus_auth_observer_new ();
    g_signal_connect (observer, "allow-mechanism", G_CALLBACK (allow_mechanism_cb), NULL);
    g_signal_connect (observer, "authorize-authenticated-peer", G_CALLBACK (authorize_authenticated_peer_cb), NULL);

    error = NULL;
    server = g_dbus_server_new_sync (opt_address,
                                     server_flags,
                                     guid,
                                     observer,
                                     NULL, /* GCancellable */
                                     &error);
    g_dbus_server_start (server);

    g_object_unref (observer);
    g_free (guid);

    if (server == NULL)
    {
        g_printerr ("Error creating server at address %s: %s\n", opt_address, error->message);
        g_error_free (error);
        goto out;
    }
    g_print ("Server is listening at: %s\n", g_dbus_server_get_client_address (server));
    g_signal_connect (server,
                      "new-connection",
                      G_CALLBACK (on_new_connection),
                      NULL);

    loop = g_main_loop_new (NULL, FALSE);
    g_main_loop_run (loop);

    g_object_unref (server);
    g_main_loop_unref (loop);

    g_dbus_node_info_unref (introspection_data);

    ret = 0;

out:
    return ret;
}